from signal import signal, SIGTERM, SIGHUP
import os
import zmq

from dmqservice import DMQService
import znode
import log

from admapi import admincommands, rep


class NoPidFile(Exception):
    """docstring for NoPidFile"""
    def __init__(self, arg):
        super(NoPidFile, self).__init__(arg)
#NoPidFile


class DMQMaster(DMQService):

    def __init__(self, user, group, pidfile, app_settings_file,
                zmq_admin={},
                workers=2,
                single_proc=False,
                daemonize=False,
                unix_socket_dir='/tmp/dmq_sockets',
                whocaresabountzombies=True,
                fio={},
        ):
        """docstring for DMQMaster"""

        super(DMQMaster, self).__init__(unix_socket_dir=unix_socket_dir)

        self.user = user
        self.group = group

        self.stderr = '/dev/null'
        self.stdin = '/dev/null'
        self.stdout = '/dev/null'
        if 'stderr' in fio:
            self.stderr = fio['stderr']
        if 'stdin' in fio:
            self.stdin = fio['stdin']
        if 'stdout' in fio:
            self.stdout = fio['stdout']

        self.pidfile = pidfile
        self.single_proc = single_proc
        self.daemonize = daemonize
        self.whocaresabountzombies = whocaresabountzombies
        self.zmq_admin = zmq_admin
        self._num_workers = workers
        self._worker_nodes = {}

        # Setup our admin connection for the outside world to
        # communicate with us on.
        self._admin_znode = znode.znode(self._admin_app,
                            type=zmq.REP,
                            transport=zmq_admin['transport'],
                            port=zmq_admin['port'],
                            endpoint=zmq_admin['host'])
        self._znodes.append(self._admin_znode)
    # __init__()

    def _kill_children(self, signum):
        """docstring for _kill_children"""

        # propagate the signal to our children
        for pid, val in self._worker_nodes.items():
            try:
                while 1:
                    os.kill(pid, signum)
                    time.sleep(0.1)
            except OSError, err:
                err = str(err)
                if not err.find("No such process") > 0:
                    print(err)
                    log.log(err, 'error')
    #_kill_children()

    def _catch_sigterm(self, signum, frame):
        """docstring for _catch_sigterm"""
        log.log("DMQMaster._catch_sigterm", 'debug')

        self._kill_children(signum)
        self.delpid()
        super(DMQMaster, slef)._catch_sigterm(signum, frame)
    #_catch_sigterm()

    def _catch_sighup(self, signum, frame):
        """docstring for _catch_sighup"""
        log.log("DMQMaster._catch_sighup", 'debug')

        self._kill_children(signum)
        self.delpid()
        super(DMQMaster, self)._catch_sighup(signum, frame)
    #_catch_sighup()

    def _catch_sigint(self, signum, frame):
        """docstring for _catch_sigint"""
        log.log("DMQMaster._catch_sigint", 'debug')

        self._kill_children(signum)
        self.delpid()
        super(DMQMaster, self)._catch_sigint(signum, frame)
    #_catch_sigint()

    def _write_pid(self):
        """docstring for _write_pid"""
        logging.debug("BaseDaemon::_write_pid pid file.")

        if not self.whocaresabountzombies and os.path.exists(self.pidfile):
            # If the pid file exists, we bail out.
            logging.error(("DMQMaster::_write_pid pid file : %s,"
                        " already exists. Bailing out.") % (self.pidfile,))
            self._suicide(1)

        try:
            pid = str(os.getpid())
            open(self.pidfile, 'w+').write("%s\n" % pid)
        except Exception as e:
            logging.error(("DMQMaster::_write_pid Error while writing "
                    "pid file : %s" % (e,))
            self._suicide(1)
    #_write_pid()

    def _delpid(self):
        """
        Delete this processes PID file.

        .. note::

            SELinux will not let root delete any processes pid
            file root does not
            own, so *delpid()* will change to the user/group the service is
            set to run as and then delete the pid file.
            This issue occurs when the SELinux security context
            type initrc_var_run_t
            is set on the /var/run directory.

        """

        logging.debug(("BaseDaemon::delpid: deleting %s. uid:%d, gid:%d. "
                "file state:%s") % (self.pidfile, os.getuid(), os.getgid(),
                                os.stat(self.pidfile)))

        try:
            logging.debug("BaseDaemon::delpid deleting %s" % self.pidfile)
            os.remove(self.pidfile)
        except Exception, e:
            logging.error(e)
            raise
    #_delpid()

    def _daemonize(self):
        """
        Do the UNIX double-fork magic, see Stevens' "Advanced
        Programming in the UNIX Environment" for details (ISBN 0201563177)

        `ISBN 0201563177 on Amazon <http://www.amazon.com/gp/search/ref=
        sr_adv_b/?search-alias=stripbooks&unfiltered=1&field-keywords=
        &field-author=&field-title=&field-isbn=978-0201563177&field-publisher=
        &node=&field-p_n_condition-type=&field-feature_browse-bin=
        &field-binding_browse-bin=&field-subject=&field-language=&field-dateop=
        &field-datemod=&field-dateyear=&sort=
        relevanceexprank&Adv-Srch-Books-Submit.x=-181&Adv-Srch-Books-Submit.y=
        -192>`_
        """
        log.log( "DMQMaster::daemonize %s" % ( str( self ) ), 'info' )

        pid = 0
        try:
            pid = os.fork()
            if pid > 0:
                # exit first parent
                log.log( "DMQMaster::daemonize exit first parent", 'debug' )
                sys.exit(0)
        except OSError, e:
            sys.stderr.write("fork #1 failed: %d (%s)\n" %
                    (e.errno, e.strerror))
            log.log("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
            sys.exit(1)

        # decouple from parent environment
        log.log( "DMQMaster::daemonize decoupled from first parent.", 'debug' )
        os.chdir("/")
        os.setsid()
        os.umask(0)

        # do second fork
        log.log( "DMQMaster::daemonize excute second fork.", 'debug' )
        try:
            pid = os.fork()
            if pid > 0:
                # exit from second parent
                log.log( "DMQMaster::daemonize exit second parent", 'debug' )
                sys.exit(0)

        except OSError as e:
            sys.stderr.write("pid:%s, fork #2 failed: %s, %s (%s)\n"
                    % (str(pid), e, str(e.errno), e.strerror))
            log.log( "pid:%s, fork #2 failed: %s, %s (%s)\n"
                    % (str(pid), e, str(e.errno), e.strerror), 'error' )
            raise
            sys.exit(1)

        try:
            # redirect standard file descriptors
            logging.debug(("DMQMaster::daemonize Forking done, "
                        "redirecting file descriptors."))
            sys.stdout.flush()
            sys.stderr.flush()
            si = file(self.stdin, 'r')
            so = file(self.stdout, 'a+')
            se = file(self.stderr, 'a+', 0)
            os.dup2(si.fileno(), sys.stdin.fileno())
            os.dup2(so.fileno(), sys.stdout.fileno())
            os.dup2(se.fileno(), sys.stderr.fileno())

            self._write_pid()

            if os.getuid() == 0:
                logging.debug(("DMQMaster::daemonized, changing to user:%s, "
                            "group:%s.") % ( self.user, self.group ))
                # pwd.getpwnam will raise a KeyError
                # if the user/group cannot be found.
                uid = pwd.getpwnam(self.user)[2]
                gid = pwd.getpwnam(self.group)[3]
                os.chmod(self.pidfile, 0644)
                os.chown(self.pidfile, uid, gid)
                os.setreuid(uid)
                os.setregid(gid)
            else:
                logging.warning(("DMQMaster::daemonized, not running as root,"
                    "unable to change to user:%s and group:%s.") %
                    ( self.user, self.group ))

        except Exception, e:
            raise e
            sys.exit(1)

        logging.debug( "DMQMaster::daemonized, done.")
    #_daemonize()

    def _start_workers(self):
        """docstring for _start_workers"""
        if self.single_proc:
            logging.error(("Why are you calling _start_workers() "
                    "in single processor mode!"))
            raise Exception("Called _start_workers() in single process mode")

        logging.info("Starting %d workers" % (self._num_workers,))

        # The daemonized process will be our 'master' process.
        # It will be in charge
        # of managing, killing of the child processes
        for p in range(self._workers):
            logging.debug("_start_workers() staring child %d of %d" %
                (p+1, self._workers))
            try:
                pid = os.fork()
                if pid > 0:
                    wpc.log(("DMQMaster::_start_workers started child, "
                            "me:%d, child:%d") %
                            (os.getpid(),pid), 'debug')
                    # Open up an admin socket for the new worker
                    admin_socket_path = self._worker_admin_socket_str % (pid,)
                    self._workers[pid] = {}
                    #self._workers[pid] = {
                    #       'admin_socket_path' : admin_socket_path,
                    #       'admin_socket' : znode.znode(self.admin_app,
                    #                  type=zmq.REQ,
                    #                  transport=znoce.zmq_transport_IPC,
                    #                  endpoint=admin_socket_path),
                    #}
                else:
                    signal(SIGTERM, self._catch_sigterm)
                    signal(SIGHUP, self._catch_sighup)
                    signal(SIGINT, self._catch_sigint)
                    self.set_proc_name('wipiped')
                    proc_name = self.get_proc_name()
                    logging.debug(("DMQMaster::_start_workers child "
                                "started %s:%d") %(proc_name, os.getpid()))
                    # log.logger_setpid(os.getpid())

                    # Start the worker
                    from dmqworker import DMQWorker
                    dw = DMQWorker(self._unix_socket_dir)

                    return

            except OSError, e:
                sys.stderr.write("fork of extra child failed %d (%s)\n" %
                                (e.errno, e.strerror))
                wpc.log("fork of extra child failed %d (%s)\n" %
                                (e.errno, e.strerror))
                sys.exit(1)
    #_start_workers()

    def _admin_worker_app(self, msg, env):
        """docstring for _admin_worker_app"""
        pass
    #_admin_worker_app()

    def _admin_app(self, msg, env):
        """docstring for _admin_app

        UUID ID SIZE:PATH,SIZE:METHOD,SIZE:PARAMS]
        UUID and ID can be NULL, 0=NULL.
        """

        logging.debug("DMQMaster._admin_app(msg:%s, env:%s)" % (msg, env))

        cmd = rep.resolve_adm_path(msg)
        return cmd.run(self)
    #_admin_app()

    def start(self):
        """docstring for start"""
        logging.debug("DMQMaster::sart")

        if self.daemonize:
            logging.debug("DMQMaster::start daemonizing")
            self._daemonize()
        else:
            logging.debug("DMQMaster::start running in the foreground")

        self.set_proc_name('dmq_master')

        if self.single_proc:
            logging.debug(("DMQMaster::start Single proccess "
                        "mode, starting worker"))
            from dmqworker import DMQWorker
            # Set up the workers admin socket
            logging.debug(("DMQMaster.start() setting up worker admin "
                        "socket on %s" %
                        (self.admin_worker_socket_path,))
            self._worker_nodes[os.getpid()] = znode.znode(
                    self._admin_worker_app,
                    type=zmq.REQ,
                    transport=znode.zmq_transport_IPC,
                    endpoint=self.admin_worker_socket_path
                    )
            DMQWorker()
            #dw = DMQWorker()
            #dw.run()
        else:
            # Start the workers
            logging.debug(("DMQMaster::start Multi process mode, "
                        "starting worker processes"))
            #self._start_workers()
            #self.run()

        logging.debug("DMQMaster::start Running dmq!")
        self.run()
        logging.debug("DMQMaster::start dmq is done, returning.")
    #start()

    def stop(self):
        """
        Stop the running daemon represented by this instance.

        First we'll check for a PID file, if doesn't exist
        we'll write a message
        to stdout and exit. If a PID is found, we will try to kill the process
        with the PID by sending it the :exc:`signal`.SIGTERM signal and then
        we will remove the PID file as well.
        """
        logging.debug("DMQMaster::stop")

        # Get the pid from the pidfile
        try:
            pf = file(self.pidfile,'r')
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None

        if not pid:
            message = "pidfile %s does not exist. DMQMaster not running?\n"
            sys.stderr.write(message % self.pidfile)
            raise NoPidFile("pid file %s not found" % (self.pidfile))

        # Try killing the daemon process
        try:
            while 1:
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
        except OSError, err:
            err = str(err)
            if err.find("No such process") > 0:
                if os.path.exists(self.pidfile):
                    self.delpid()
            else:
                print str(err)
                sys.exit(1)
    #stop()

    def admin_app(self, msg, env):
        """docstring for admin_app"""
        pass
    #admin_app()
#DMQMaster
